package com.clementheliou.cheliou.dal.model.photo.builder;

import static org.fest.util.Arrays.array;

import java.math.BigDecimal;
import java.util.Set;

import org.joda.time.LocalDateTime;
import org.springframework.util.Assert;

import com.clementheliou.cheliou.dal.dao.camera.brand.CameraBrandDAO;
import com.clementheliou.cheliou.dal.dao.camera.model.CameraModelDAO;
import com.clementheliou.cheliou.dal.dao.city.CityDAO;
import com.clementheliou.cheliou.dal.dao.country.CountryDAO;
import com.clementheliou.cheliou.dal.dao.photo.PhotoDAO;
import com.clementheliou.cheliou.dal.model.camera.brand.builder.CameraBrandBuilder;
import com.clementheliou.cheliou.dal.model.camera.model.CameraModel;
import com.clementheliou.cheliou.dal.model.camera.model.builder.CameraModelBuilder;
import com.clementheliou.cheliou.dal.model.city.City;
import com.clementheliou.cheliou.dal.model.city.builder.CityBuilder;
import com.clementheliou.cheliou.dal.model.country.builder.CountryBuilder;
import com.clementheliou.cheliou.dal.model.photo.AuthorsNote;
import com.clementheliou.cheliou.dal.model.photo.Photo;
import com.clementheliou.cheliou.dal.model.photo.enumeration.Direction;
import com.clementheliou.cheliou.dal.model.photo.enumeration.FlashMode;
import com.clementheliou.cheliou.dal.model.photo.enumeration.WhiteBalance;
import com.clementheliou.cheliou.util.builder.ObjectBuilder;
import com.clementheliou.cheliou.util.time.TimeUtils;

/**
 * Builder used to get {@link Photo} instances for test purpose. It must only be used through one of
 * its static factory methods.
 * 
 * @author Clément HELIOU (clement.heliou@gmail.com)
 * @see {@link ObjectBuilder};
 * @see {@link PhotoBuilder}.
 * @since 1.0
 */
public class PhotoBuilder extends ObjectBuilder<Photo> {

	/**
	 * The {@link CameraBrandDAO} to be transmitted to the {@link CameraBrandBuilder} if the
	 * instance must be persisted.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @see {@link CameraBrandBuilder} .
	 * @since 1.0
	 */
	private final CameraBrandDAO cameraBrandDAO;

	/**
	 * The {@link CameraModelDAO} to be transmitted to the {@link CameraModelBuilder} if the
	 * instance must be persisted.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @see {@link CameraModelBuilder} .
	 * @since 1.0
	 */
	private final CameraModelDAO cameraModelDAO;

	/**
	 * The {@link CityDAO} to be transmitted to the {@link CityBuilder} if the instance must be
	 * persisted.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @see {@link CityBuilder} .
	 * @since 1.0
	 */
	private final CityDAO cityDAO;

	/**
	 * The {@link CountryDAO} to be transmitted to the {@link CountryBuilder} if the instance must
	 * be persisted.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @see {@link CountryBuilder} .
	 * @since 1.0
	 */
	private final CountryDAO countryDAO;

	/**
	 * The {@link PhotoDAO} to be used to persist the built instance if needed.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	private final PhotoDAO photoDAO;

	/**
	 * Private default constructor.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	private PhotoBuilder() {
		this(null, null, null, null, null);
	}

	/**
	 * Private constructor from the DAO's to be used.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @param cameraBrandDAO the {@link CameraBrandDAO} to be used.
	 * @param cameraModelDAO the {@link CameraModelDAO} to be used.
	 * @param cityDAO the {@link CityDAO} to be used.
	 * @param countryDAO the {@link CountryDAO} to be used.
	 * @param photoDAO the {@link PhotoDAO} to be used.
	 * @since 1.0
	 */
	private PhotoBuilder(CameraBrandDAO cameraBrandDAO, CameraModelDAO cameraModelDAO, CityDAO cityDAO, CountryDAO countryDAO, PhotoDAO photoDAO) {
		this.cameraBrandDAO = cameraBrandDAO;
		this.cameraModelDAO = cameraModelDAO;
		this.cityDAO = cityDAO;
		this.countryDAO = countryDAO;
		this.photoDAO = photoDAO;
	}

	/**
	 * Static factory method used to get a builder instance for a {@link Photo} object that must be
	 * persistent. It's mostly used on data access layer's methods tests.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @param cameraBrandDAO the {@link CameraBrandDAO} to be used.
	 * @param cameraModelDAO the {@link CameraModelDAO} to be used.
	 * @param cityDAO the {@link CityDAO} to be used.
	 * @param countryDAO the {@link CountryDAO} to be used.
	 * @param photoDAO the {@link PhotoDAO} to be used.
	 * @return the builder instace ready to be used.
	 * @since 1.0
	 */
	public static PhotoBuilder with(CameraBrandDAO cameraBrandDAO, CameraModelDAO cameraModelDAO, CityDAO cityDAO, CountryDAO countryDAO, PhotoDAO photoDAO) {
		return new PhotoBuilder(cameraBrandDAO, cameraModelDAO, cityDAO, countryDAO, photoDAO);
	}

	/**
	 * Static factory method used to get a builder instance for a {@link Photo} object that must not
	 * be persistent. It's mostly used in tests not having a real data access.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @return the builder instance to be used.
	 * @since 1.0
	 */
	public static PhotoBuilder withNoDependencies() {
		return new PhotoBuilder();
	}

	/**
	 * Overrides the addition date of the default instance with the given value.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @param additionDate the addition date to be used.
	 * @return the builder instance after this action.
	 * @since 1.0
	 */
	public PhotoBuilder overridingAdditionDate(final LocalDateTime additionDate) {
		getObject().setAdditionDate(additionDate);
		return this;
	}

	/**
	 * Overrides the authors notes of the default instance with the given value.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @param authorsNotes the value to be used.
	 * @return the builder instance after this action.
	 * @since 1.0
	 */
	public PhotoBuilder overridingAuthorsNotes(final Set<AuthorsNote> authorsNotes) {
		getObject().setAuthorsNotes(authorsNotes);
		return this;
	}

	/**
	 * Overrides the code of the default instance with the given value.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @param code the code to be used.
	 * @return the builder instance after this action.
	 * @since 1.0
	 */
	public PhotoBuilder overridingCode(final String code) {
		getObject().setCode(code);
		return this;
	}

	/**
	 * Overrides the title key of the default instance with the given value.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @param titleKey the title key to be used.
	 * @return the builder instance after this action.
	 * @since 1.0
	 */
	public PhotoBuilder overridingTitleKey(final String titleKey) {
		getObject().setTitleKey(titleKey);
		return this;
	}

	/**
	 * Creates a default {@link Photo} instance, ready to be persisted.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @return the builder instance after this action.
	 * @since 1.0
	 */
	public PhotoBuilder createDefaultPhoto() {
		setInstance(createInstance());
		return this;
	}

	/**
	 * Creates a default {@link Photo} mock that should never be persisted.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @return the builder instance after this action.
	 * @since 1.0
	 */
	public PhotoBuilder createDefaultPhotoMock() {
		setInstance(createInstance(new CameraModel(), new City()));
		return this;
	}

	/**
	 * Persists the built {@link Photo} instance then flush the current session.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @return the builder instance after this action.
	 * @since 1.0
	 */
	public PhotoBuilder persistAndFlush() {
		Assert.notNull(photoDAO, "photoDAO must not be null at this time");

		photoDAO.saveEntity(getObject());
		photoDAO.flushSession();
		return this;
	}

	/**
	 * Creates a {@link Photo} instance with default values. These ones have a functional sense. The
	 * instance's hash code is added to the fields that need to be uniques.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @return the built instance.
	 * @see {@link Object#hashCode()}.
	 * @since 1.0
	 */
	private Photo createInstance() {
		Assert.notNull(cameraBrandDAO, "cameraBrandDAO must not be null at this time");
		Assert.notNull(cameraModelDAO, "cameraModelDAO must not be null at this time");
		Assert.notNull(cityDAO, "cityDAO must not be null at this time");
		Assert.notNull(countryDAO, "countryDAO +must not be null at this time");

		final CameraModel cameraModel = CameraModelBuilder.with(cameraBrandDAO, cameraModelDAO).createDefaultCameraModel().persistAndFlush().getObject();
		return createInstance(cameraModel, CityBuilder.with(cityDAO, countryDAO).createDefaultCity().persistAndFlush().getObject());
	}

	/**
	 * Creates a {@link Photo} instance with default values from the given camera model and city.
	 * Created data have functional sense. The instance's hash code is added to the fields that need
	 * to be uniques.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @param cameraModel the camera model used to take the created photo.
	 * @param city the city where the created photo has been taken.
	 * @return the built instance.
	 * @since 1.0
	 */
	private Photo createInstance(final CameraModel cameraModel, final City city) {
		final Photo defaultPhoto = new Photo();

		defaultPhoto.setAdditionDate(TimeUtils.now());
		defaultPhoto.setBlackAndWhite(true);
		defaultPhoto.setBrightnessModified(false);
		defaultPhoto.setCameraModel(cameraModel);
		defaultPhoto.setCity(city);
		defaultPhoto.setCode("code" + defaultPhoto.hashCode()); // To ensure unicity.
		defaultPhoto.setContrastModified(false);
		defaultPhoto.setDirection(Direction.LANDSCAPE);
		defaultPhoto.setExposureTime(2);
		defaultPhoto.setFlashModes(java.util.Arrays.asList(array(FlashMode.FLASH_DID_NOT_FIRE)));
		defaultPhoto.setFocal(new BigDecimal("1.2"));
		defaultPhoto.setFocalDistance(2);
		defaultPhoto.setFocalDistance35mm(4);
		defaultPhoto.setIso(200);
		defaultPhoto.setLatitude(new BigDecimal("42.4556"));
		defaultPhoto.setLayersModified(false);
		defaultPhoto.setLongitude(new BigDecimal("2.354"));
		defaultPhoto.setModel(false);
		defaultPhoto.setRelativeAperture(new BigDecimal("2.3"));
		defaultPhoto.setShootingDate(TimeUtils.now());
		defaultPhoto.setStraighteningUp(true);
		defaultPhoto.setTitleKey("titleKey");
		defaultPhoto.setWhiteBalanceMode(WhiteBalance.AUTO);

		return defaultPhoto;
	}
}
